package org.metasketch.simulator.transformation.demo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.metasketch.demo.model.ActorRole;
import org.metasketch.demo.model.CoordinationLink;
import org.metasketch.demo.model.CoordinationStep;
import org.metasketch.demo.model.CoordinationStepKind;
import org.metasketch.demo.model.DEMOModel;
import org.metasketch.demo.model.OrganizationalFunction;
import org.metasketch.demo.model.TransactionKind;
import org.metasketch.demo.model.TransactionStep;
import org.metasketch.demo.project.DEMOProject;

public class GetValidElements {

	// only actor roles which are part of an org function are deemed valid.
	// because only org functions can be assigned to actual instances - aka
	// people, staff.
	public static List<ActorRole> getValidActorRoles(DEMOProject project) {

		List<ActorRole> roles = new ArrayList<ActorRole>();

		DEMOModel model = (DEMOModel) project.getModel();
		List<ActorRole> actorRoles = model.getActorRoles();
		List<OrganizationalFunction> orgFunctions = model
				.getOrganizationalFunctions();

		// actor roles are valid if they belong to an organizational function
		for (OrganizationalFunction organizationalFunction : orgFunctions) {
			Set<ActorRole> containedRoles = organizationalFunction
					.getContainedActorRoles();
			for (ActorRole actorRole : containedRoles) {

				if (actorRole.getAssignedTo() == null) {
					// TODO test if it belongs toa n org unit as well. This
					// should
					// be true...
					// throw warning otherwise...
					// continue;
				}

				if (roles.contains(actorRole))
					continue;

				roles.add(actorRole);
			}
		}

		// actor roles are also valid if they don't belong to an org function,
		// but not also to a org unit. that is, they are external.
		for (ActorRole role : actorRoles) {

			if (role.getAssignedTo() == null) {
				// test to see if it belongs to an org function...
				for (OrganizationalFunction organizationalFunction : orgFunctions) {
					Set<ActorRole> containedRoles = organizationalFunction
							.getContainedActorRoles();
					if (containedRoles.contains(role)) {

						if (roles.contains(role))
							break;

						roles.add(role);

					}
				}
			}

		}

		return roles;

	}

	public static List<ActorRole> getValidExternalActorRoles(DEMOProject project) {

		List<ActorRole> roles = new ArrayList<ActorRole>();

		DEMOModel model = (DEMOModel) project.getModel();
		List<ActorRole> actorRoles = model.getActorRoles();
		List<OrganizationalFunction> orgFunctions = model
				.getOrganizationalFunctions();

		// actor roles are also valid if they don't belong to an org function,
		// but not also to a org unit. that is, they are external.
		for (ActorRole role : actorRoles) {

			if (role.getAssignedTo() == null) {
				// test to see if it belongs to an org function...
				for (OrganizationalFunction organizationalFunction : orgFunctions) {
					Set<ActorRole> containedRoles = organizationalFunction
							.getContainedActorRoles();
					if (!containedRoles.contains(role)) {

						if (roles.contains(role))
							continue;

						roles.add(role);

					}
				}
			}

		}

		return roles;
	}

	// get which org functions an actor role belongs to
	public static List<OrganizationalFunction> getOrgFunctions(
			DEMOProject project, ActorRole role) {

		List<OrganizationalFunction> functions = new ArrayList<OrganizationalFunction>();

		DEMOModel model = (DEMOModel) project.getModel();
		List<OrganizationalFunction> positive = new ArrayList<OrganizationalFunction>();
		List<OrganizationalFunction> negative = new ArrayList<OrganizationalFunction>();
		// List<ActorRole> actorRoles = model.getActorRoles();
		List<OrganizationalFunction> orgFunctions = model
				.getOrganizationalFunctions();
		for (OrganizationalFunction organizationalFunction : orgFunctions) {
			if (doesOrgFunctionPlayActorRole(organizationalFunction, role,
					positive, negative))
				functions.add(organizationalFunction);
		}
		// for (OrganizationalFunction orgFunc : orgFunctions) {
		// Set<ActorRole> containedRoles = orgFunc.getContainedActorRoles();
		// if (containedRoles.contains(role)) {
		// if (!functions.contains(orgFunc))
		// functions.add(orgFunc);
		// }
		//
		// }
		//
		// for (OrganizationalFunction orgFunc : orgFunctions) {
		// Set<OrganizationalFunction> inherited = orgFunc
		// .getInheritedOrganizationalFunctions();
		//
		// }

		return functions;

	}

	public static boolean doesOrgFunctionPlayActorRole(
			OrganizationalFunction function, ActorRole role,
			List<OrganizationalFunction> positive,
			List<OrganizationalFunction> negative) {

		if (function.getContainedActorRoles().contains(role)) {
			positive.add(function);
			return true;
		}

		Set<OrganizationalFunction> inherited = function
				.getInheritedOrganizationalFunctions();
		for (OrganizationalFunction org : inherited) {

			if (positive.contains(org)) {
				positive.add(function);
				return true;
			} else if (negative.contains(org)) {
				continue;
			} else {
				if (doesOrgFunctionPlayActorRole(org, role, positive, negative)) {
					positive.add(function);
					return true;
				} else {
					continue;
				}
			}

		}

		negative.add(function);
		return false;

	}

	public static List<TransactionKind> getIndependentTransactions(
			DEMOProject project) {

		List<TransactionKind> transactions = new ArrayList<TransactionKind>();

		List<TransactionKind> dependent = new ArrayList<TransactionKind>();

		DEMOModel model = (DEMOModel) project.getModel();

		List<TransactionKind> trans = model.getTransactions();
		for (TransactionKind transaction : trans) {
			Set<TransactionStep> steps = transaction.getSteps();
			for (TransactionStep step : steps) {
				Set<CoordinationLink> links = step.getOwnedCoordinationLinks();
				for (CoordinationLink link : links) {

					// no need to test the links that go to steps of the same
					// transaction
					if (steps.contains(link.getTarget()))
						continue;

					if (link.getTarget().getParentTransaction() != transaction) {
						if (!dependent.contains(transaction)) {
							dependent.add(transaction);
						}

						if (transactions.contains(transaction)) {
							transactions.remove(transaction);
						}
					}
				}
			}

			if (!dependent.contains(transaction))
				transactions.add(transaction);

		}

		return transactions;

	}

	public static List<TransactionKind> getValidIndependentTransactionsRoles(
			DEMOProject project) {

		List<TransactionKind> transactions = new ArrayList<TransactionKind>();

		List<TransactionKind> trans = getIndependentTransactions(project);
		List<ActorRole> roles = getValidActorRoles(project);

		List<TransactionKind> testedValid = new ArrayList<TransactionKind>();
		List<TransactionKind> testedFalse = new ArrayList<TransactionKind>();

		for (TransactionKind transaction : trans) {
			if (recursiveGetValidIndependentRoles(transaction, roles,
					testedValid, testedFalse))
				transactions.add(transaction);
		}

		return transactions;

	}

	protected static boolean recursiveGetValidIndependentRoles(
			TransactionKind transaction, List<ActorRole> validRoles,
			List<TransactionKind> testedValid, List<TransactionKind> testedFalse) {

		if (testedValid.contains(transaction))
			return true;
		if (testedFalse.contains(transaction))
			return false;

		Set<TransactionStep> steps = transaction.getSteps();
		// boolean invalid = false;
		for (TransactionStep step : steps) {
			if (!validRoles.contains(step.getActorRole())) {
				testedFalse.add(transaction);
				return false;
			}

			Set<CoordinationLink> links = step.getOwnedCoordinationLinks();
			for (CoordinationLink link : links) {

				// no need to test the links that go to steps of the same
				// transaction
				if (steps.contains(link.getTarget()))
					continue;

				if (link.getTarget().getParentTransaction() != transaction) {
					if (!recursiveGetValidIndependentRoles(link.getTarget()
							.getParentTransaction(), validRoles, testedValid,
							testedFalse)) {

						testedFalse.add(transaction);
						return false;
					}

				}

			}

		}

		testedValid.add(transaction);
		return true;
	}

	public static List<TransactionKind> getValidTransactionsActorRoles(
			DEMOProject project) {

		List<TransactionKind> transactions = new ArrayList<TransactionKind>();

		List<TransactionKind> invalid = new ArrayList<TransactionKind>();

		DEMOModel model = (DEMOModel) project.getModel();

		List<TransactionKind> trans = model.getTransactions();
		for (TransactionKind transactionKind : trans) {
			if (transactionKind.getExecutedBy().size() > 0
					&& transactionKind.getInitiatedBy().size() > 0) {
				transactions.add(transactionKind);
			}
		}

		return transactions;

	}

	public static List<TransactionKind> getValidTransactionsReferences(
			DEMOProject project) {

		DEMOModel model = (DEMOModel) project.getModel();
		List<TransactionKind> trans = model.getTransactions();

		List<TransactionKind> validTransactions = GetValidElements
				.getValidTransactionsActorRoles(project);

		List<TransactionKind> invalid = new ArrayList<TransactionKind>();
		invalid.addAll(trans);
		invalid.removeAll(validTransactions);

		Map<String, List<String>> references = getReferences(validTransactions);

		Map<String, List<String>> invalidReferences = new HashMap<String, List<String>>();

		List<TransactionKind> tempInvalid = new ArrayList<TransactionKind>();
		// tempInvalid.addAll(invalid);

		do {

			tempInvalid.clear();

			for (TransactionKind candidate : validTransactions) {
				List<String> transRefs = references.get(candidate.getId());

				for (TransactionKind invTransaction : invalid) {
					if (transRefs.contains(invTransaction.getId())) {

						if (!tempInvalid.contains(candidate))
							tempInvalid.add(candidate);

						break;
					}
				}

			}

			invalid.addAll(tempInvalid);
			validTransactions.removeAll(tempInvalid);

			// repeat this while we're still finding invalid transactions
		} while (tempInvalid.size() > 0);

		// check circular references

		getValidTransactionsNoCircularReferences(validTransactions, references);

		return validTransactions;
	}

	private static List<TransactionKind> getValidTransactionsNoCircularReferences(
			List<TransactionKind> validTransactions,
			Map<String, List<String>> references) {

		// List<TransactionKind> valids = new ArrayList<TransactionKind>();

		List<TransactionKind> tempInvalid = new ArrayList<TransactionKind>();

		// List<String> path = new ArrayList<String>();

		do {

			tempInvalid.clear();

			for (TransactionKind candidate : validTransactions) {

				String candidateId = candidate.getId();

				if (!recursiveCheckPath(references, candidateId, null))
					tempInvalid.add(candidate);

			}

			// invalid.addAll(tempInvalid);
			validTransactions.removeAll(tempInvalid);

			// repeat this while we're still finding invalid transactions
		} while (tempInvalid.size() > 0);

		return validTransactions;

	}

	private static boolean recursiveCheckPath(
			Map<String, List<String>> references, String candidateId,
			List<String> path) {

		List<String> transRefs = references.get(candidateId);

		if (transRefs == null || transRefs.size() == 0)
			return true;

		for (String string : transRefs) {
			if (path == null)
				path = new ArrayList<String>();
			else {
				if (path.contains(string)) {
					return false;
				}
			}

			path.add(string);

			if (!recursiveCheckPath(references, string, path))
				return false;

		}

		return true;
	}

	// get child composition references only...
	private static Map<String, List<String>> getReferences(
			List<TransactionKind> validTransactions) {
		Map<String, List<String>> references = new HashMap<String, List<String>>();

		// add references to other transactions
		for (TransactionKind transactionKind : validTransactions) {

			CoordinationStep promise = transactionKind
					.getFirstCoordinationStep(CoordinationStepKind.Promise);

			// Set<TransactionStep> steps = transactionKind.getSteps();
			// for (TransactionStep step : steps) {
			Set<CoordinationLink> links = promise.getOwnedCoordinationLinks();
			for (CoordinationLink link : links) {
				TransactionKind nextTransaction = link.getTarget()
						.getParentTransaction();
				if (nextTransaction != transactionKind) {

					List<String> refs = references.get(transactionKind.getId());
					if (refs == null) {
						refs = new ArrayList<String>();
						references.put(transactionKind.getId(), refs);
					}

					refs.add(nextTransaction.getId());

				}
			}
			// }
		}
		return references;
	}

}
